package vip.xiaonuo.common.util;

import lombok.extern.slf4j.Slf4j;
import vip.xiaonuo.common.dto.GpsPoint;

/**
 * 地球坐标系——WGS84：常见于 GPS 设备，Google 地图等国际标准的坐标体系。
 * 火星坐标系——GCJ-02：中国国内使用的被强制加密后的坐标体系，高德坐标就属于该种坐标体系。
 * 百度坐标系——BD-09：百度地图所使用的坐标体系，是在火星坐标系的基础上又进行了一次加密处理。
 */
@Slf4j
public class GpsUtil {

    /**
     * 圆周率π
     */
    //  public final static double PI = Math.PI; // 3.14159265358979323846
    public final static double PI = 3.1415926535897932384626D; //
    /**
     * 长半轴
     */
    public final static double AXIS = 6378245.0D;
    /**
     * 扁率
     */
    private final static double OFFSET = 0.00669342162296594323; // (a^2 - b^2) / a^2

    /**
     * 火星坐标系与百度坐标系转换的中间量
     */
    private final static double X_PI = PI * 3000.0 / 180.0D;

    /**
     * GCJ-02=>BD09 火星坐标系=>百度坐标系
     *
     * @param point 经纬度
     * @return BD09
     */
    public static GpsPoint gcj2BD09(GpsPoint point) {
        double x = point.getLon();
        double y = point.getLat();
        double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * X_PI);
        double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * X_PI);
        double bd_lng = z * Math.cos(theta) + 0.0065;
        double bd_lat = z * Math.sin(theta) + 0.006;
        return new GpsPoint(bd_lat, bd_lng);
    }

    /**
     * BD09=>GCJ-02 百度坐标系=>火星坐标系
     *
     * @param point 经纬度
     * @return GCJ-02
     */
    public static GpsPoint bd092GCJ(GpsPoint point) {
        double x = point.getLon() - 0.0065;
        double y = point.getLat() - 0.006;
        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI);
        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI);
        double gcj_lng = z * Math.cos(theta);
        double gcj_lat = z * Math.sin(theta);
        return new GpsPoint(gcj_lat, gcj_lng);
    }

    /**
     * BD09=>WGS84 百度坐标系=>地球坐标系
     *
     * @param point 经纬度
     * @return WGS84
     */
    public static GpsPoint bd092WGS(GpsPoint point) {
        return gcj2WGS(bd092GCJ(point));
    }

    /**
     * WGS84=》BD09 地球坐标系=>百度坐标系
     *
     * @param wgLat 纬度
     * @param wgLon 经度
     * @return BD09
     */
    public static GpsPoint wgs2BD09(double wgLat, double wgLon) {
        return gcj2BD09(wgs2GCJ(new GpsPoint(wgLat, wgLon)));
    }

    /**
     * WGS84=》BD09 地球坐标系=>百度坐标系
     *
     * @param point 纬度/经度
     * @return
     */
    public static GpsPoint wgs2BD09(GpsPoint point) {
        return gcj2BD09(wgs2GCJ(point));
    }

    /**
     * WGS84=》GCJ02 地球坐标系=>火星坐标系
     *
     * @param point 经纬度
     * @return WGS84
     */
    public static GpsPoint wgs2GCJ(GpsPoint point) {
        if (outOfChina(point.getLat(), point.getLon())) {
            return point;
        }
        GpsPoint delta = delta(point);
        return new GpsPoint(point.getLat() + delta.getLat(), point.getLon() + delta.getLon());
    }

    /**
     * GCJ02=>WGS84 火星坐标系=>地球坐标系(粗略)
     *
     * @param point 经纬度
     * @return WGS84
     */
    public static GpsPoint gcj2WGS(GpsPoint point) {
        if (outOfChina(point.getLat(), point.getLon())) {
            return point;
        }
        GpsPoint delta = delta(point);
        return new GpsPoint(point.getLat() - delta.getLat(), point.getLon() - delta.getLon());
    }


    /**
     * GCJ02=>WGS84 火星坐标系=>地球坐标系（精确）
     *
     * @param point 经纬度
     * @return WGS84
     */
    public static GpsPoint gcj2WGS84(GpsPoint point) {
        double initDelta = 0.01;
        double threshold = 0.000000001;
        double dLat = initDelta, dLon = initDelta;
        double mLat = point.getLat() - dLat, mLon = point.getLon() - dLon;
        double pLat = point.getLat() + dLat, pLon = point.getLon() + dLon;
        double wgsLat, wgsLon, i = 0;
        while (true) {
            wgsLat = (mLat + pLat) / 2;
            wgsLon = (mLon + pLon) / 2;
            GpsPoint tmp = wgs2GCJ(new GpsPoint(wgsLat, wgsLon));
            dLat = tmp.getLat() - point.getLat();
            dLon = tmp.getLon() - point.getLon();
            if ((Math.abs(dLat) < threshold) && (Math.abs(dLon) < threshold)) {
                break;
            }
            if (dLat > 0) {
                pLat = wgsLat;
            } else {
                mLat = wgsLat;
            }
            if (dLon > 0) {
                pLon = wgsLon;
            } else {
                mLon = wgsLon;
            }

            if (++i > 10000) {
                break;
            }
        }
        return new GpsPoint(wgsLat, wgsLon);
    }

    public static GpsPoint delta(GpsPoint point) {
        double dLat = transformLat(point.getLon() - 105.0, point.getLat() - 35.0);
        double dLon = transformLon(point.getLon() - 105.0, point.getLat() - 35.0);
        double radLat = point.getLat() / 180.0 * PI;
        double magic = Math.sin(radLat);
        magic = 1 - OFFSET * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((AXIS * (1 - OFFSET)) / (magic * sqrtMagic) * PI);
        dLon = (dLon * 180.0) / (AXIS / sqrtMagic * Math.cos(radLat) * PI);
        return new GpsPoint(dLat, dLon);
    }

    public static double transformLat(double x, double y) {
        double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(y / 12.0 * PI) + 320 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0;
        return ret;
    }

    public static double transformLon(double x, double y) {
        double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0;
        return ret;
    }

    public static boolean outOfChina(double lat, double lon) {
        if (lon < 72.004 || lon > 137.8347) {
            return true;
        }
        return lat < 0.8293 || lat > 55.8271;
    }

    /*
     * 保留小数点后六位
     * @param num
     * @return
     */
    public static double retain6(double num){
        String result = String .format("%.6f", num);
        return Double.valueOf(result);
    }


    public static void main(String[] args) {
        GpsPoint gpsPoint=new GpsPoint();
        gpsPoint.setLat(29.589826666666667);
        gpsPoint.setLon(103.76356333333334);
        GpsPoint gpsPointtemp =  GpsUtil.gcj2BD09(gpsPoint);
        System.out.println(gpsPointtemp);
    }
}
